package service

import (
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"gitee.com/InsignificantDust/msrv/files"
)

type dockerService struct {
	// 服务名称
	srvName string
	// cmdType
	srvLang string
	// 命令参数
	cmdArgs string
	// 完整包名,不含路径
	pkgName string
	// 工作目录
	workDir string
	// 服务包目录
	pkgDir string
	// 源包目录
	pkgSourceDir string
	// 备份目录
	backupDir string
	// 源配置文件目录
	srcConfigDir string
	// 配置文件目录
	configDir string
	// PidFile
	pidFile string
	// 健康检查url
	healthUrl string
	// 服务启动程序命令
	runCmd string
	// 服务启动超时时间
	startTimeout int
	// 文件后缀
	pkgPostfix string
	// 备份后缀
	bakPostfix string
	// 包来源
	pkgFrom string
	// 如果需要下载时请提供下载url, 从本地为空字符串
	pkgUrl string
	// 服务描述信息
	description string
}

func (j *dockerService) SrvName() string {
	return j.srvName
}

func (j *dockerService) Start() error {
	fmt.Printf("启动服务%s\n", j.srvName)
	if j.Status() {
		fmt.Printf("服务%s正在运行中\n", j.srvName)
		return nil
	}
	dockerCmd, _ := j.GetCmd()
	if dockerCmd == "" {
		return NewServiceError(j.srvName, "执行服务检查失败：服务启动程序命令不存在")
	}
	if !strings.HasSuffix(dockerCmd, "docker-compose") {
		return NewServiceError(j.srvName, "容器运行建议使用docker-compose")
	}
	if err := os.Chdir(j.workDir); err != nil {
		return NewServiceError(j.srvName, "服务启动失败：", err.Error())
	}
	var args []string
	args = append(args, dockerCmd)
	args = append(args, "run")
	args = append(args, "-d")

	attr := &os.ProcAttr{Dir: j.workDir, Env: os.Environ(), Files: []*os.File{nil, os.Stdout, os.Stderr}}
	process, err := os.StartProcess(dockerCmd, args, attr)
	if err != nil {
		fmt.Println(err)
		return err
	}
	chkCounter := j.startTimeout

	for {
		time.Sleep(chkInterval * time.Second)
		if chkCounter < 0 {
			process.Kill()
			return NewServiceError(j.srvName, "服务启动失败", "服务启动超时！")
		}
		if j.Status() {
			break
		}
		chkCounter = chkCounter - 5
	}
	fmt.Printf("服务%s启动成功\n", j.srvName)

	return nil
}

func (j *dockerService) Stop() error {
	fmt.Printf("关闭服务%s\n", j.srvName)
	if !j.Status() {
		return nil
	}
	dockerCmd, _ := j.GetCmd()
	if dockerCmd == "" {
		return NewServiceError(j.srvName, "服务运行命令"+j.runCmd+"不存在")
	}

	if !strings.HasSuffix(dockerCmd, "docker-compose") {
		return NewServiceError(j.srvName, "容器运行建议使用docker-compose")
	}
	var args []string
	args = append(args, dockerCmd)
	args = append(args, "down")
	args = append(args, "-v")
	attr := &os.ProcAttr{Dir: j.workDir, Env: os.Environ(), Files: []*os.File{nil, os.Stdout, os.Stderr}}
	process, err := os.StartProcess(dockerCmd, args, attr)
	if err != nil {
		return err
	}
	processState, err := process.Wait()
	if processState.Exited() {
		return nil
	}
	return nil
}

func (j *dockerService) Restart() error {
	if err := j.Stop(); err != nil {
		return err
	}
	if err := j.Start(); err != nil {
		return err
	}
	return nil
}

func (j *dockerService) Deploy() error {
	fmt.Printf("部署服务%s......\n", j.srvName)
	if !files.IsDirExist(j.workDir) {
		if err := files.Mkdir(j.workDir); err != nil {
			return NewServiceError(j.srvName, "部署失败：", err.Error())
		}
	}
	if j.configDir != "" {
		if files.IsDirExist(j.configDir) || files.IsExist(j.configDir) {
			err := files.CopyDir(j.srcConfigDir, j.configDir, true)
			if err != nil {
				return NewServiceError(j.srvName, "部署失败：", err.Error())
			}
		}
	} else {
		return NewServiceError(j.srvName, "部署失败：未配置compose配置文件")
	}
	if err := j.Start(); err != nil {
		return err
	}
	return nil
}

func (j *dockerService) Status() bool {
	if j.healthUrl == "" {
		return false
	}
	resp, err := http.Get(j.healthUrl)
	if err != nil {
		return false
	}
	if resp.StatusCode != 200 {
		return false
	}
	return true
}

func (j *dockerService) Rollback(updateConfig bool) error {
	fmt.Printf("服务%s回滚\n", j.srvName)
	config := filepath.Base(j.configDir)
	bakConfig := filepath.Join(j.backupDir, config)
	currentConfig := filepath.Join(j.workDir, config)
	if files.IsDirExist(bakConfig) || files.IsExist(bakConfig) {
		if err := files.CopyDir(bakConfig, currentConfig, true); err != nil {
			return NewServiceError(j.srvName, "服务回滚失败：", err.Error())
		}
	} else {
		return NewServiceError(j.srvName, "服务回滚失败：备份配置文件不存在")
	}
	if err := j.Restart(); err != nil {
		return err
	}
	return nil
}

func (j *dockerService) Update(updateConfig bool) error {
	fmt.Println("服务更新中......")
	if err := j.backup(updateConfig); err != nil {
		return NewServiceError(j.srvName, "更新失败：", err.Error())
	}
	if err := j.Stop(); err != nil {
		return NewServiceError(j.srvName, "更新失败：", err.Error())
	}
	fmt.Println("执行更新")
	if j.configDir != "" && files.IsDirExist(j.srcConfigDir) && updateConfig {
		if j.configDir == "" {
			return NewServiceError(j.srvName, "更新失败：未配置配置文件！")
		}
		if files.IsDirExist(j.srcConfigDir) || files.IsExist(j.srcConfigDir) {
			err := files.CopyDir(j.srcConfigDir, j.configDir, true)
			if err != nil {
				return NewServiceError(j.srvName, "更新失败：", err.Error())
			}
		} else {
			return NewServiceError(j.srvName, "更新失败：配置文件不存在！")
		}

	}
	if err := j.Start(); err != nil {
		return NewServiceError(j.srvName, "更新失败：", err.Error())
	}
	fmt.Println("服务更新完成")
	return nil
}

// 备份服务
func (j *dockerService) backup(updateConfig bool) error {
	if !files.IsDirExist(j.backupDir) {
		fmt.Println("服务备份不目录不存在，创建备份目录")
		if err := files.Mkdir(j.backupDir); err != nil {
			return NewServiceError(j.srvName, "更新失败：", err.Error())
		}
	}
	if updateConfig {
		baseConfig := filepath.Base(j.configDir)
		bakConfig := filepath.Join(j.backupDir, baseConfig)
		if files.IsDirExist(bakConfig) {
			if err := os.RemoveAll(bakConfig); err != nil {
				return NewServiceError(j.srvName, "更新失败：", err.Error())
			}
		}
		if err := files.CopyDir(j.configDir, bakConfig, true); err != nil {
			return NewServiceError(j.srvName, "更新失败：", err.Error())
		}
	}
	return nil
}

func (j *dockerService) Uninstall() error {
	fmt.Println("卸载服务......")
	if err := j.Stop(); err != nil {
		return NewServiceError(j.srvName, "卸载失败：", err.Error())
	}
	if err := files.RemoveDir(j.workDir); err != nil {
		return NewServiceError(j.srvName, "卸载失败：", err.Error())
	}
	fmt.Println("卸载成功")
	return nil
}

func (j *dockerService) GetCmd() (string, error) {
	if filepath.IsAbs(j.runCmd) && files.IsExist(j.runCmd) && files.IsExecutable(j.runCmd) {
		return j.runCmd, nil
	}
	cmdPath, err := exec.LookPath(j.runCmd)
	if cmdPath != "" {
		return cmdPath, nil
	}
	return "", err
}

func (j *dockerService) Validate() bool {
	return true
}

func (j *dockerService) Describ() string {
	sourcePkg := filepath.Join(j.pkgSourceDir, j.pkgName)
	descStr := fmt.Sprintf("%s服务由%s编写而成，源码包为: %s,工作目录：%s ,配置目录：%s\n", j.srvName, j.srvLang, sourcePkg, j.workDir, j.configDir)
	return descStr
}
